package com.spzx.product.service.impl;

import com.spzx.common.core.utils.StringUtils;
import com.spzx.common.core.utils.uuid.UUID;
import com.spzx.product.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Service
public class TestServiceImpl implements TestService {

   @Autowired
   private StringRedisTemplate stringRedisTemplate;

   /**
    * V1版本锁
    */
   // @Override
   public synchronized void testLockV1() {
      // 查询Redis中的num值
      String value = stringRedisTemplate.opsForValue().get("num");
      // 没有该值return
      if (StringUtils.isBlank(value)){
         return ;
      }
      // 有值就转成int
      int num = Integer.parseInt(value);
      // 把Redis中的num值+1
      stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
   }

   /**
    * V2版本锁
    * 1. 使用 redis的 setnx命令，java中的 setIfAbsent()方法，获取到锁返回 true，否则返回 false
    * 2. 为了释放锁，必须将释放锁的代码放在 finally中，保证释放锁的代码一定执行
    * 3. 设置超时时间要注意确保原子性
    * 4. 问题：可能误删其它锁，在 V3版本中解决
    */
   // @Override
   public void testLockV2() {
      Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
      // 设置超时时间（缺乏原子性），建议在 setIfAbsent()方法后面设置
      stringRedisTemplate.expire("lock", 3, TimeUnit.SECONDS);

      // 判断是否获取锁成功
      if (flag) {
         try {
            // 业务逻辑
            String value = stringRedisTemplate.opsForValue().get("num");

            int a = 1/0;

            if (StringUtils.isBlank(value)){
               return ;
            }
            int num = Integer.parseInt(value);
            stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
         } catch (Exception e) {
            throw new RuntimeException(e);
         } finally {
            // 释放锁
            stringRedisTemplate.delete("lock");
         }
      } else {
         // 注意：要间隔重试，不然会销毁大量 CPU资源
         try {
            TimeUnit.MILLISECONDS.sleep(1000);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         testLock();
      }
   }

   /**
    * V3版本锁
    * 使用 uuid作为锁的值，防止误删，将锁的过期时间设置的长一些
    * 问题：释放锁时，存在原子性问题
    */
   // @Override
   public void testLockV3() {
      String uuid = UUID.randomUUID().toString();
      // 在 set时指定过期时间
      Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);

      // 判断是否获取锁成功
      if (flag) {
         try {
            // 业务逻辑
            String value = stringRedisTemplate.opsForValue().get("num");

            int a = 1/0;

            if (StringUtils.isBlank(value)){
               return ;
            }
            int num = Integer.parseInt(value);
            stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
         } catch (Exception e) {
            throw new RuntimeException(e);
         } finally {
            // 释放锁
            if (uuid.equals(stringRedisTemplate.opsForValue().get("lock"))) {
               // 存在原子性问题：jvm1中的 equals方法执行完，锁恰好过期，jvm2获得锁，jvm1删除的锁是 jvm2的锁
               // 使用 Lua脚本解决
               stringRedisTemplate.delete("lock");
            }
         }
      } else {
         // 注意：要间隔重试，不然会销毁大量 CPU资源
         try {
            TimeUnit.MILLISECONDS.sleep(1000);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         testLock();
      }
   }

   /**
    * V4版本锁
    * 使用 Lua脚本解决，多个 redis之间不具备原子性，但是 Lua脚本具有原子性，用 Lua脚本
    * 运行多个 redis即可保证 redis释放锁具有原子性
    */
   @Override
   public void testLock() {
      String uuid = UUID.randomUUID().toString();
      // 在 set时指定过期时间
      Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);

      // 判断是否获取锁成功
      if (flag) {
         try {
            // 业务逻辑
            String value = stringRedisTemplate.opsForValue().get("num");

            // int a = 1/0;

            if (StringUtils.isBlank(value)){
               return ;
            }
            int num = Integer.parseInt(value);
            stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
         } catch (Exception e) {
            throw new RuntimeException(e);
         } finally {
            // Lua脚本
            String script = """
                       if redis.call('get', KEYS[1]) == ARGV[1] then
                           return redis.call('del', KEYS[1])
                       else
                           return 0
                       end
                       """;
            // 释放锁
            Boolean lock = stringRedisTemplate.execute(
                    new DefaultRedisScript<Boolean>(script, Boolean.class),
                    Arrays.asList("lock"),
                    uuid);
            System.out.println("锁释放成功：" + lock);
            /*
            if (uuid.equals(stringRedisTemplate.opsForValue().get("lock"))) {
               // 存在原子性问题：jvm1中的 equals方法执行完，锁恰好过期，jvm2获得锁，jvm1删除的锁是 jvm2的锁
               // 使用 Lua脚本解决
               stringRedisTemplate.delete("lock");
            }
            */
         }
      } else {
         // 注意：要间隔重试，不然会销毁大量 CPU资源
         try {
            TimeUnit.MILLISECONDS.sleep(1000);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         testLock();
      }
   }
}